package cn.itcast.nems.product.scheduler;

import cn.hutool.core.lang.Assert;
import cn.itcast.ic.common.exception.v2.BizExceptionProducer;
import cn.itcast.nems.common.tenant.TenantInfo;
import cn.itcast.nems.common.utils.BigDecimalUtil;
import cn.itcast.nems.manager.util.CustomBeanUtil;
import cn.itcast.nems.product.dao.dataobject.BizNo;
import cn.itcast.nems.product.dao.dataobject.ProductDO;
import cn.itcast.nems.product.dao.dataobject.TuitionFeeDO;
import cn.itcast.nems.product.dao.dataobject.TuitionFeePhaseDO;
import cn.itcast.nems.product.dao.entity.ProductPriceRule;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;

/**
 * @author liyong
 * create: 2023-09-04 13:45:02
 **/
public class ProductPriceRuleCal extends ProductPriceCal {
    private final LocalDate MIN_DATE = LocalDate.of(1900, 1, 1);
    private final LocalDate MAX_DATE = LocalDate.of(9999, 12, 31);

    private final LocalTime MIN_TIME = LocalTime.of(0, 0, 0);
    private final LocalTime MAX_TIME = LocalTime.of(23, 59, 59);
    private final LocalDateTime MIN_DATE_TIME = LocalDateTime.of(MIN_DATE, MIN_TIME);
    private final LocalDateTime MAX_DATE_TIME = LocalDateTime.of(MAX_DATE, MAX_TIME);

    private final List<ProductPriceRule> result = new ArrayList<>();
    protected final String operator;

    public ProductPriceRuleCal(List<ProductDO> products,
                               List<TuitionFeeDO> tuitionFees,
                               Map<Integer, List<TuitionFeePhaseDO>> phases,
                               Map<Integer, Set<String>> schools,
                               String operator) {
        super(products, tuitionFees, phases, schools);
        this.operator = operator;
    }

    public List<ProductPriceRule> calculate() {
        super.products.forEach(product -> {
            BizNo bizNo = product.getBizNoObject();
            final Integer phaseCount = super.clazzPhaseCounts.get(bizNo.getClazzId());
            final List<TuitionFeeDO> targetTuitionFees = this.findTuitionFees(product, phaseCount);
            if (!targetTuitionFees.isEmpty()) {
                TuitionFeeDO tuitionFee = targetTuitionFees.get(0);
                if (TuitionFeeDO.TIME_DIMENSION_CLASS_OPEN.equalsIgnoreCase(tuitionFee.getTimeDimension())) {
                    this.createRuleByClassOpen(targetTuitionFees, product, phaseCount);
                } else {
                    this.createRuleBySingUp(targetTuitionFees, product, phaseCount);
                }
            }
        });
        return result;
    }

    public List<ProductPriceRule> getResult() {
        return result;
    }

    private void createRuleByClassOpen(List<TuitionFeeDO> targetTuitionFees, ProductDO product, int phaseCount) {
        TuitionFeeDO tuitionFee = null;
        for (TuitionFeeDO fee : targetTuitionFees) {
            if (fee.getUseDate().compareTo(product.getEffectiveDatetime()) <= 0) {
                tuitionFee = fee;
                break;
            }
        }
        if (tuitionFee == null) {
            logger.warn("产品{}没有找到价格信息", product.getBizNo());
            return;
        }
        if (phaseCount == 1) {
            LocalDate startDate = tuitionFee.getStartDate();
            if (startDate == null) {
                startDate = MIN_DATE_TIME.toLocalDate();
            }
            LocalDate endDate = tuitionFee.getEndDate();
            if (endDate == null) {
                endDate = MAX_DATE_TIME.toLocalDate();
            }
            this.createProductPriceRule(product, tuitionFee, startDate.atStartOfDay(), endDate.atTime(23, 59, 59));
        } else {
            final TuitionFeePhaseDO tuitionFeePhase = this.findTuitionFeePhase(tuitionFee, product);
            this.createProductPriceRuleByPhase(product, tuitionFeePhase, MIN_DATE_TIME, MAX_DATE_TIME);
        }
    }

    private void createRuleBySingUp(List<TuitionFeeDO> targetTuitionFees, ProductDO product, int phaseCount) {
        if (phaseCount == 1) {
            this.calPriceRuleIntervals2(targetTuitionFees,
                    product,
                    (p,
                     tuitionFee,
                     feeEffectiveDatetime,
                     feeExpireDatetime) ->
                            this.createProductPriceRule(p, tuitionFee, feeEffectiveDatetime, feeExpireDatetime));
        } else {
            this.calPriceRuleIntervals(targetTuitionFees,
                    product,
                    (p,
                     tuitionFee,
                     feeEffectiveDatetime,
                     feeExpireDatetime) -> {
                        final TuitionFeePhaseDO feePhase = this.findTuitionFeePhase(tuitionFee, p);
                        this.createProductPriceRuleByPhase(p, feePhase, feeEffectiveDatetime, feeExpireDatetime);
                    });
        }
    }

    // 按起止日期计算
    private void calPriceRuleIntervals2(List<TuitionFeeDO> tuitionFees,
                                        ProductDO product,
                                        PriceRuleIntervalConsumer consumer) {
        if (tuitionFees == null || tuitionFees.isEmpty()) {
            return;
        }
        tuitionFees.sort(new TuitionFeeDOCompare());
        for (int i = 0; i < tuitionFees.size(); i++) {
            TuitionFeeDO fee = tuitionFees.get(i);
            final LocalDateTime feeEffectiveDatetime = LocalDateTime.of(fee.getStartDate(), MIN_TIME);
            final LocalDateTime feeExpireDatetime = LocalDateTime.of(fee.getEndDate(), MAX_TIME);
            consumer.accept(product, fee, feeEffectiveDatetime, feeExpireDatetime);
        }
    }

    // 按使用时间计算
    private void calPriceRuleIntervals(List<TuitionFeeDO> tuitionFees,
                                       ProductDO product,
                                       PriceRuleIntervalConsumer consumer) {
        if (tuitionFees == null || tuitionFees.isEmpty()) {
            return;
        }
        LocalDate productStartDate = this.calStartDate(product);
        LocalDate productEndDate = this.calEndDate(product);
        tuitionFees.sort(Comparator.comparing(TuitionFeeDO::getUseDate));
        for (int i = 0; i < tuitionFees.size(); i++) {
            TuitionFeeDO fee = tuitionFees.get(i);
            final LocalDate feeEffectiveDate = fee.getUseDate();
            final LocalDateTime feeEffectiveDatetime = LocalDateTime.of(feeEffectiveDate, MIN_TIME);
            final LocalDate feeExpireDate = i < (tuitionFees.size() - 1) ? tuitionFees.get(i + 1).getUseDate().minusDays(1) : MAX_DATE;
            final LocalDateTime feeExpireDatetime = LocalDateTime.of(feeExpireDate, MAX_TIME);
            if (!(feeEffectiveDate.compareTo(productEndDate) > 0 || feeExpireDate.compareTo(productStartDate) < 0)) {
                consumer.accept(product, fee, feeEffectiveDatetime, feeExpireDatetime);
            }
        }
    }

    private TuitionFeePhaseDO findTuitionFeePhase(TuitionFeeDO tuitionFee, ProductDO product) {
        final List<TuitionFeePhaseDO> tuitionFeePhases = super.phases.get(tuitionFee.getId());
        TuitionFeePhaseDO result = null;
        if (tuitionFeePhases != null) {
            BizNo bizNo = product.getBizNoObject();
            for (TuitionFeePhaseDO feePhase : tuitionFeePhases) {
                if (feePhase.getPhaseNumber().equals(String.valueOf(bizNo.getPhase()))) {
                    result = feePhase;
                    break;
                }
            }
        }
        return result;
    }

    private LocalDate calStartDate(ProductDO product) {
        return Objects.requireNonNull(CustomBeanUtil.getEffectiveDatetime(product.getEffectiveDatetime())).minusDays(180);
    }

    private LocalDate calEndDate(ProductDO product) {
        Assert.notNull(product.getEffectiveDatetime(), BizExceptionProducer.serverError("产品编号 %s, 待定班生效时间为空", product.getBizNo()));
        // 如果是待定班
        if (CustomBeanUtil.isPendingCLass(product.getEffectiveDatetime())) {
            return Objects.requireNonNull(CustomBeanUtil.getEffectiveDatetime(product.getEffectiveDatetime())).plusDays(180);
        }
        LocalDate endDate = product.getExpireDatetime();
        if (endDate == null) {
            endDate = Objects.requireNonNull(CustomBeanUtil.getEffectiveDatetime(product.getEffectiveDatetime())).plusDays(180);
        }
        return endDate;
    }

    private List<TuitionFeeDO> findTuitionFees(ProductDO product, int phaseCount) {
        List<TuitionFeeDO> result = new ArrayList<>();
        for (TuitionFeeDO fee : tuitionFees) {
            if (super.productMatchTuitionFee(product, fee, phaseCount)) {
                result.add(fee);
            }
        }
        return result;
    }

    private void createProductPriceRule(ProductDO product,
                                        TuitionFeeDO tuitionFee,
                                        LocalDateTime effectiveDatetime,
                                        LocalDateTime expireDatetime) {
        if (tuitionFee != null) {
            this.createProductPriceRule(product, tuitionFee.getFirst(), tuitionFee.getLast(), effectiveDatetime, expireDatetime);
        } else {
            logger.warn("产品{}没有找到价格信息", product.getBizNo());
        }
    }

    private void createProductPriceRuleByPhase(ProductDO product,
                                               TuitionFeePhaseDO tuitionFeePhase,
                                               LocalDateTime effectiveDatetime,
                                               LocalDateTime expireDatetime) {
        if (tuitionFeePhase != null) {
            this.createProductPriceRule(product, tuitionFeePhase.getFirst(), tuitionFeePhase.getLast(), effectiveDatetime, expireDatetime);
        } else {
            logger.warn("产品{}没有找到阶段价格信息", product.getBizNo());
        }
    }

    private void createProductPriceRule(ProductDO product,
                                        BigDecimal first,
                                        BigDecimal last,
                                        LocalDateTime effectiveDatetime,
                                        LocalDateTime expireDatetime) {
        ProductPriceRule rule = new ProductPriceRule();
        rule.setProductId(product.getId());
        int price = first == null ? 0 : BigDecimalUtil.bigDecimalToCentInt(first);
        int postpaidPrice = last == null ? 0 : BigDecimalUtil.bigDecimalToCentInt(last);
        rule.setPrice(price);
        rule.setPostpaidPrice(postpaidPrice);
        rule.setListPrice(price);
        rule.setEffectiveDatetime(effectiveDatetime);
        rule.setExpireDatetime(expireDatetime);
        rule.setCreatedBy(operator);
        rule.setCreatedDatetime(LocalDateTime.now());
        rule.setTenantId(TenantInfo.DEFAULT_TENANT_ID);
        rule.setSubtenantId(TenantInfo.DEFAULT_SUBTENANT_ID);
        this.result.add(rule);
    }
}
